/*
 * codename: xEngine
 * Copyright (C) Aljosa Osep <aljosa.osep@gmail.com>
 *
 * Based on xSoko engine (C) Aljosa Osep, Jernej Skrabec, Jernej Halozan, Martin Savc 2008 <aljosa.osep@gmail.com, jernej.skrabec@gmail.com, jernej.halozan@gmail.com, martin.savc@gmail.com>
 *
 * xEngine project is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * xEngine project is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/* 
 * File: game.cpp
 *
 * Summary:
 * Includes game core implementation
 *
 * Author: Aljosa Osep 2007
 * Changes:
 * Aljosa May 28 2008
 */

#include <SDL/SDL.h>
#include <SDL/SDL_opengl.h>

#include <fstream>
#include <cstdio>
#include <cmath>
#include <boost/filesystem.hpp>

#include "engine.h"
#include "gui/forms.h"
#include "config.h"
#include "zip/zipfile.h"

#include "RenderingSystem.h"
#include "game.h"
#include "EventSystem.h"
#include "ResourceManager.h"

// TODO: APPS INCLUDES MOVE TO apps.h !!!
#include "climbingGame/VirtualScriptExample.h"
#include "TerrainExample.h"


//using namespace xEngine;
using namespace boost::filesystem;

namespace xEngine
{
      namespace CoreSystem
      {
          // constructors
          Engine::Engine() : isAppRunning(false), gameQuit(false), application(NULL)
          { 
              //
          }

          bool Engine::init(int _width, int _height, string _title/*, Game *app*/)
          {
              // Basic engine initialization is done here
              Messages::mainTitleMessage(); // Welcome message
              windowTitle = _title; // Set window title

              // Sets homepath, OS specific
              #ifdef _WINDOWS
                  homepath = string(getenv("HOMEDRIVE")) + "\\" + string(getenv("HOMEPATH")) + "\.xsoko";
              #else
                  homepath = string(getenv("HOME")) + "/.xsoko";
              #endif


              path dir_path(homepath); // Home directory

              // Check if there is home direcotry
              if(!exists(dir_path) && !create_directory(dir_path)) {
                  Messages::errorMessage("Could not find home directory!");
                  return false;
              }

              // Initialize SDL subsystem
              if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) != 0)  // was SDL initialization successful?
              {
                  Messages::initMessage("SDL", false); // Tell user that something went wrong
                  Messages::initMessage("SDL audio", false); // Tell user that else something went wrong
                  terminate(); // terminate and return false
                  return false;
              }

              Messages::initMessage("SDL", true); // prints out that initialization was success
              Messages::initMessage("SDL audio", true);

              // Attempt to read configuration file
              if(!Config::ReadConfig(homepath + "/xsoko.conf"))
              {
                  Messages::errorMessage("Reading configuration file failed! (NOTE: this is ok at first run)");

                  // Set default values
                  Config::SetValueInt("xres",_width);
                  Config::SetValueInt("yres",_height);
                  Config::SetValueBool("fullscreen",false);
              }

              // Config file found, read resolution and fullscreen info
              windowWidth = Config::GetValueInt("xres");
              windowHeight = Config::GetValueInt("yres");
              fullscreen = Config::GetValueBool("fullscreen");
              unsigned flag = fullscreen ? SDL_FULLSCREEN : 0;

              // Set SDL atributtes and surface for rendering
              SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER,1);
              SDL_Surface* screen = SDL_SetVideoMode( windowWidth, windowHeight, 32, SDL_OPENGL | flag );

              // 800 x 600, 16 bit color, no depth, alpha or stencil buffers, windowed
              if (screen == NULL)
              {
                  Messages::infoMessage("OpenGL window creation failed. Reverting to default mode.");
                  // could not create OpenGL screen, try default mode
                  Config::SetValueInt("xres",_width);
                  Config::SetValueInt("yres",_height);
                  Config::SetValueBool("fullscreen",false);
                  windowWidth = _width;
                  windowHeight = _height;
                  fullscreen = false;
                  flag = fullscreen ? SDL_FULLSCREEN : 0;
                  screen = SDL_SetVideoMode( windowWidth, windowHeight, 32, SDL_OPENGL | flag );

                //  Messages::infoMessage(to)
                  

		  // default mode did not work either, exit
                  if (screen == NULL)
                  {
                        Messages::errorMessage("OpenGL window creation failed.");  // failed
                        terminate();
                        return false;
                  }
              }

              Messages::initMessage("OpenGL window", true);  // prints out success
              cout<<windowWidth<<"x"<<windowHeight<<", 32bit"<<endl;
              SDL_WM_SetCaption(this->windowTitle.c_str(),this->windowTitle.c_str()); // set window caption

              // And now do GUI
              Messages::infoMessage("Initialiazing GUI...");
              
              const SDL_VideoInfo* vi = SDL_GetVideoInfo();
              Gui::getInst().onResolutionChange(vi->current_w, vi->current_h);
              #if defined(Linux_Release) || defined(Windows_Release) || defined(_RELEASE)
                  SDL_ShowCursor(SDL_DISABLE);
              #endif

              // Create main and game menu
              mainMenu = new MainMenu();
              gameMenu = new GameMenu();

              // Init resource manager
              Resources::ResourceManager::getInstance().init();

              // Init RenderingSystem singleton
              // It's instance is constructed here.
              Renderer::RenderingSystem::get()->init();

              // Init event system
              xEngine::CoreSystem::Events::EventSystem::getInstance().init();
              Messages::initMessage("Event handler", true);



             // sm = SoundManager();
            //  sm.loadEffect("bomb","data/sound/bombtiq.wav"); //TODO: nalaganje soudna bombe se naj izvede samo, če so kake bombe v levelu
             // sm.loadMusic("guardian","data/sound/mirror.mp3");
              //sm.playMusic("guardian");
            //  sm.loadMusic("mortal","data/sound/mortal_kombat.mp3");
              //sm.playMusic("mortal");

            //  level.reset(new PLevel());
           //   level->initialize();
           //   input.setLevel(level.get());
          //    camera = level->getGameCoreHandle()->getCamera();

              // Time to init our game
            //  this->application = app; // Set application passed by argument
           //   this->setApplication( app );
          /*    if ( this->application != NULL ) // If there is an application
              {
                  if ( this->application->init() ) // Initialize it
                      Messages::initMessage("Game application", true); // And inform the logging system
                  else
                  {
                      Messages::initMessage("Game application", false); // Initialization fail case
                      this->terminate();
                      return false;
                  }
              }
              else // App is NULL, that means we don't have the app at all
              {
                  Messages::errorMessage("Game object is NULL. Terminating.");
                  this->terminate();
                  return false;
              }*/

              Messages::initMessage("Core systems", true);  // Inform logging system that all core systems were successfully initialized.
              return true;
          }

          // Sets game quit flag to true
          void Engine::quit(){
              gameQuit = true;
          }

          // Sets application to run
          void Engine::setApplication(Game *app) {
              if ( this->application != NULL ) { // We have app running - stop it and delete it
                 this->application->shutDown(); // Shut down out app
                 delete this->application; // Delete object from memory
                 this->application = NULL; // And set pointer to NULL

                 // Write state into the console
                 Messages::infoMessage("Game application shut down and released from memory.");
              }

              // Set the application
              this->application = app;

              // Init
              if ( this->application != NULL ) // If there is an application
              {
                  if ( this->application->init() ) // Initialize it
                      Messages::initMessage("Game application", true); // And inform the logging system
                  else
                  {
                      Messages::initMessage("Game application", false); // Initialization fail case
                    //  this->terminate();
                  }
              }
              else // App is NULL, that means we don't have the app at all
              {
                  Messages::errorMessage("Game object is NULL. Terminating.");
                //  this->terminate();
              }
          }

          // Sets resolution
          void Engine::setResolution(int width, int height, bool fullscreen)
          {
              bool isChange = false;

              if(fullscreen != this->fullscreen)
              {
                  this->fullscreen = fullscreen;
                  isChange = true;
              }
              unsigned flag = fullscreen ? SDL_FULLSCREEN : 0;

              if((windowWidth != width) || (windowHeight != height))
              {
                  windowWidth = width;
                  windowHeight = height;
                  isChange = true;
              }

              if(isChange)
              {
                  if(SDL_SetVideoMode( width, height, 32, SDL_OPENGL | flag ))
                      Gui::getInst().onResolutionChange(width,height);
		  #ifdef _WINDOWS
                    GuiRender::getInstance().reloadSkin();
		    FontManager::getInstance().reload();
                    level.reset(new PLevel());
                    input.setLevel(level.get());
		  #endif
                  Config::SetValueInt("xres",width);
                  Config::SetValueInt("yres",height);
                  Config::SetValueBool("fullscreen",fullscreen);
              }
          }

          // ===============================================================
          // The infamus game loop!
          // ===============================================================
          void Engine::mainLoop()
          {
            Messages::infoMessage("Entering main loop...");

            // the time of the previous frame
            double old_time = ((double)SDL_GetTicks())/1000.0;
            double old_time_FPS = old_time;

            // In debug mode, show FPS
            #if defined(Linux_Debug) || defined(Windows_Debug) || defined(_DEBUG)
                unsigned frames = 0;
                string title;
            #endif

            unsigned msgid = 0;
            bool menuVisible = false;

            // Roll game loop while gameQuit flag is set
            while(!gameQuit)
            {
                // Calculate previus-frame time
                double current_time = ((double)SDL_GetTicks())/1000.0;//glfwGetTime();
                double diff_time = current_time - old_time;
                old_time = current_time;

                glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
                glLoadIdentity(); // reset view matrix



                // In debug mode, show FPS
                #if defined(Linux_Debug) || defined(Windows_Debug) || defined(_DEBUG)
                    if(current_time - old_time_FPS >= 1){
                      title = "xEngine FPS: " + Functions::toString<int>(frames);
                      // the second argument is the title of the minimized window
                      SDL_WM_SetCaption(title.c_str(), title.c_str());//glfwSetWindowTitle(title.c_str());
                      old_time_FPS = current_time;
                      frames = 0;
                    } else
                        frames ++;
                #endif

                input.process();

 


                

//////////////// GUI ///////////////

                if(isAppRunning || msgid){

                    //if no win message is shown, check for input etc.
                    if(!msgid){

                        //level->processBombs(current_time);

                        if(input.toggleGameMenu() != menuVisible){
                            menuVisible = !menuVisible;
                            gameMenu->setVisible(menuVisible);
                            Gui::getInst().setMouseVisible(menuVisible);
                        }

                        // is game over? or level done?
//                       /if(level->getEndgameFlag()) {
//                            if(gamepack && curLevel < numLevels) {
//                                zifstream file(pack,Functions::toString(++curLevel)+".lvl");
//                                forceLevelQuit = file.good() ? !level->loadLevel(file) : true;
//                                if(forceLevelQuit)
//                                    Messages::errorMessage("Level loading failed!");
//                            } else {
//                                gamepack = false;
//                                levelLoaded = false;
//                                msgid = Gui::getInst().showMessage("xSoko", "Congratulations, you won!");
//                                // openGameMenu switches controls to GUI
//                                input.openGameMenu();
//                                Messages::infoMessage("You won!");
//                                Gui::getInst().setMouseVisible(true);
//                            }
//                        }
                        if(forceLevelQuit) {
                            //gamepack = false;
                            isAppRunning = false;
                            gameMenu->setVisible(false);
                            mainMenu->setVisible(true);
                            input.openGameMenu();
                            Gui::getInst().setMouseVisible(true);
                        }

                    }
             

               // ACTUAL STUFF GOES HERE
                    //
                    //
                    //
                this->application->update(diff_time);

                // Clear GL. TODO: Wrap this in renderer
             //   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
              //  glLoadIdentity(); // reset view matrix
                this->application->draw(diff_time);
                this->application->animate(diff_time);
                this->application->handleInput();

                    //particles.process(delta_rotate*10);
                }
                

                if(msgid && !Gui::getInst().isMessageActive(msgid)){
                        msgid = 0;
                        mainMenu->setVisible(true);
                }

                Gui::getInst().Render();
//////////////// _____________GUI ///////////////

                

               // if(levelLoaded || msgid){

                // TODO: MUST GO FROM MAIN LOOP !!!!!
                    glMatrixMode(GL_PROJECTION);						// Select The Projection Matrix
                    glLoadIdentity();                                                         // Reset The Projection Matrix
                    // Calculate The Aspect Ratio Of The Window
                    gluPerspective(45.0f,windowWidth/(float)windowHeight,0.1f,100.0f);
                    glMatrixMode(GL_MODELVIEW);						// Select The Modelview Matrix
                    glLoadIdentity();
             //   }

                  SDL_GL_SwapBuffers();
                
            }
          }

          void Engine::terminate()
          {
              Messages::infoMessage("Termination...");
              if(!Config::SaveConfig(homepath + "/xsoko.conf"))
                  Messages::errorMessage("Writing configuration file failed! Changes will NOT be saved!");

              #if defined(Linux_Release) || defined(Windows_Release) || defined(_RELEASE)
                 SDL_ShowCursor(SDL_ENABLE);
              #endif

             // Shutdown renderer
             Renderer::RenderingSystem::get()->shutDown();

             // At last, drop game
             if ( this->application != NULL )
             {
                 this->application->shutDown(); // Shut down out app
                 delete this->application; // Delete object from memory
                 this->application = NULL; // And set pointer to NULL

                 // Write state into the console
                 Messages::infoMessage("Game application shut down and released from memory.");
             }
             
             SDL_Quit();
          }
          
       /*   void Engine::loadLevel(string levelPath){
            ifstream file(levelPath.c_str());
            if(!level->loadLevel(file)) {
                Messages::errorMessage("Level loading failed!");
                mainMenu->setVisible(true);
                return;
            }
            file.close();

	    input.closeGameMenu();
            levelLoaded = true;
            forceLevelQuit = false;
            Gui::getInst().setMouseVisible(false);
          }*/

          void Engine::loadApp(string name) {

              // TODO: fix this, make it more flexible
              if ( name == "VirtualScript") {
                  this->setApplication(new ClimbingGame::ClimbingGame);
              } else if ( name == "Terrain") {
                  this->setApplication(new TerrainExample);
              }
              else {
                  cout<<"Error, unknown applicatipon: "<<name<<endl;
                  return;
              }

             // gamepack = true;

              input.closeGameMenu();
              isAppRunning = true;
              forceLevelQuit = false;
              Gui::getInst().setMouseVisible(false);
          }

       /*   void Engine::loadGamePack(string packPath) {
              zifstream info(packPath,"info.txt");
              info >> numLevels;
              if(numLevels > 0) {
                  zifstream file(packPath,"1.lvl");
                  if(file.good()) {
                      pack = packPath;
                      if(!level->loadLevel(file)) {
                          mainMenu->setVisible(true);
                          return;
                      }
                      curLevel = 1;
                      gamepack = true;
                      input.closeGameMenu();
                      levelLoaded = true;
                      forceLevelQuit = false;
                      Gui::getInst().setMouseVisible(false);
                      return;
                  }
              }
              Messages::errorMessage("Level loading failed!");
              mainMenu->setVisible(true);
          }*/
          
          void Engine::resetLevel(){
        //      level->reset();
              input.closeGameMenu();
          }
          
          void Engine::exitLevel(){
         //     levelLoaded = false;
              gameMenu->setVisible(false);
              mainMenu->setVisible(true);
          }

          Engine& Engine::getInstance(){
              static Engine game;
              return game;
          }

        /*  SoundManager* Engine::getSoundManagerInstance()
          {
              return &sm;
          }*/
      }
}
